// #packager-optimize

(function() {

    var $u = juliet.using({
        func : 'juliet.core.func-0.1',
        dom : 'juliet.core.dom-0.1',
        Observer : 'juliet.core.Observer-0.1',
        object : 'juliet.core.object-0.1',
        DragProxy : 'juliet.ui.dragAndDrop.DragProxy-0.1'
    });

    /**
     * Used to make UI elements draggable.
     */
    var Drag = function() {

        /**
         * Options object passed on "initialize" method with default values for unset parameters.
         */
        this.options = {
            dragStartDx : 3,
            dragStartDy : 3
        };

        /**
         * Currently dragging element data.
         */
        this.activeDragElementData = null;

        /**
         * Generates and handles events.
         */
        this.observer = null;

        /**
         * Proxy for drag element.
         */
        this.proxy = null;

        /**
         * Binds mouse handlers to document.
         */
        this.bindGlobalMouseHandlers = function() {
            $(document)
                .mousemove($u('func').proxy(this.onMouseMove, this))
                .mouseup($u('func').proxy(this.onMouseUp, this));

            document.ondragstart = function() { return false };
            document.body.onselectstart = function() { return false };
        };

        /**
         * Unbinds mouse handlers from document.
         */
        this.unbindGlobalMouseHandlers = function() {
            $(document)
                .unbind('mousemove', $u('func').proxy(this.onMouseMove))
                .unbind('mouseup', $u('func').proxy(this.onMouseUp));

            document.ondragstart = null;
            document.body.onselectstart = null;
        };

        /**
         * Binds mouse handlers to draggable elements container.
         */
        this.bindDraggableElementsContainerMouseHandlers = function() {
            $('.' + this.options.containerClass).mousedown(
                $u('func').proxy(this.onMouseDown, this)
            );
        };

        /**
         * Prepares drag proxy element and shows it.
         */
        this.prepareAndShowProxy = function() {
            var activeDragElement = $(this.activeDragElementData.elementDom);

            this.proxy.setSize(activeDragElement.outerWidth(), activeDragElement.outerHeight());
            this.proxy.show();
        };

        /**
         * Checks if user starts drag an element or just clicks on it.
         */
        this.isDragging = function(e) {
            var data = this.activeDragElementData;

            if ( data.dragging ) return true;

            data.dragging = ((Math.abs(data.mouseDownCoords.x - e.pageX) > this.options.dragStartDx)
                || (Math.abs(data.mouseDownCoords.y - e.pageY) > this.options.dragStartDy));

            if ( data.dragging ) this.observer.notify('onDragStarted');

            return data.dragging;
        };

        /**
         * Checks if the dragging element has correct class.
         */
        this.isCorrectDragElement = function(el) {
            return (el && (el.className.indexOf(this.options.itemClass) != -1));
        };

        /**
         * Handles "onDragStarted" event of the observer.
         */
        this.onDragStarted = function() {
            this.prepareAndShowProxy();
        };

        this.fixTarget = function(target) {
            return ($u('dom').parent(target, this.options.itemClass, 3) || target);
        };

        /**
         * Handles draggable elements container mouse down event.
         */
        this.onMouseDown = function(e) {
            var target = this.fixTarget(e.target);

            if ( (e.which != 1) || !this.isCorrectDragElement(target) ) return;

            this.activeDragElementData = {
                elementDom : target,
                mouseDownCoords : { x : e.pageX, y : e.pageY },
                dragging : false
            };

            this.bindGlobalMouseHandlers();

            return false;
        };

        /**
         * Handles document mouse up event.
         */
        this.onMouseUp = function(e) {
            if ( !this.activeDragElementData ) return;

            this.observer.notify('onMouseUp', e);
            this.activeDragElementData = null;
            this.proxy.hide();
            this.unbindGlobalMouseHandlers();
        };

        /**
         * Handles document mouse move event.
         */
        this.onMouseMove = function(e) {
            if ( !this.activeDragElementData || !this.isDragging(e) ) return;

            this.proxy.setOffset(e.pageX, e.pageY);
            this.observer.notify('onMouseMove', e);
        };

        /**
         * Initialize internal observer.
         */
        this.initializeObserver = function() {
            this.observer = juliet.createObject($u('Observer'));
            this.observer.subscribe('onDragStarted', this.onDragStarted, this);
        };

        /**
         * Initialize instance using options
         */
        this.initialize = function(o) {
            this.proxy = juliet.createObject($u('DragProxy'));

            $u('object').merge(this.options, o);

            this.initializeObserver();
            this.proxy.initialize();
            this.bindDraggableElementsContainerMouseHandlers();
        };

    };

    juliet.registerModule('juliet.ui.dragAndDrop', 'Drag-0.1', Drag);

})();
